/*
 * Copyright (c) 2010, Christian Lerche
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the Institute nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * Author: Christian Lerche <christian.lerche@uni-rostock.de>
 *
 */

/**
 * \file The uDPWS main process
 * \author Christian Lerche <christian.lerche@uni-rostock.de>
 */

#include <stdio.h> /* For printf() */
#include "contiki.h"
#include "contiki-net.h"

#include "udpws.h"

/* Debug section as you will find it in every uDPWS file */
#define DEBUG                 UDPWS_DBG_LEVEL_PROCESS  /* 0 - No Debug -> 5 - Full Debug */
#define UDPWS_DEBUG_MODULE    "PROCESS"
#include "udpws-debug.h"

#include "dpws.h"
#include "http.h"
#include "common.h"
#include "sendm.h"

#define UDPWS_INTERFACE_NO             2
/*0 = link local, 1 = multicast, 2 = global*/

#define UDPWS_DISCOVERY_MULTICAST_PORT       3702
//#define MULTICAST_IP4_DISCOVERY           "239.255.255.250"
#define MULTICAST_IP6_DISCOVERY           "ff02::c"
#if UDPWS_PORT < 1000
#error "UDPWS_PORT must be greater or equal than 1000"
#endif
#if UDPWS_PORT > 9999
#error "UDPWS_PORT must be less or equal than 9999"
#endif

/* TODO: better place*/
static const struct uip_eth_addr ethaddr = { { 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff } };

/*---------------------------------------------------------------------------*/
PROCESS(udpws_process, "uDPWS process")
;
AUTOSTART_PROCESSES(&udpws_process);
/*---------------------------------------------------------------------------*/
int handle_tcp();
void handle_udp();
void generate_transport_address();
void uip_udp_packet_send_fix(struct uip_udp_conn *c, int len);
void handle_wsd_hello();

int read_persistent_data(uint32_t *instance_id);
int write_persistent_data(uint32_t instance_id);

extern u16_t uip_slen;
#include "net/uip-udp-packet.h"
#include <string.h>
#include "contiki-conf.h"
#include "dpws-gen.h"

/* our global dpws structure, containing everything */
struct dpws_s dpws;
struct uip_conn tcp_con;
struct uip_udp_conn *udp_conn;

#if UIP_CONF_IPV6
uip_ipaddr_t multicastaddr = { 0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xc };
#else
uip_ipaddr_t multicastaddr = { {239, 255, 255, 250}};
#endif /* UIP_CONF_IPV6 */

/* global data for sending the hello message */
static uint8_t dpws_hello_timer_count = 2;
static struct etimer dpws_hello_timer;

/* required global variables
 * TODO: move to a better place*/
/* heap for processing discovery messages */
char discovery_stack_mem[UDPWS_DISCOVERY_STACK_SIZE];
struct stack_s discovery_stack = { discovery_stack_mem, (discovery_stack_mem + UDPWS_DISCOVERY_STACK_SIZE), discovery_stack_mem };
/* ONE sending machine for our only connection
 * (for every connection one sendm mem is required)*/
char sendm_mem[SENDM_MAX_ITEMS * SENDM_ITEM_SIZE];
char http_transport_addr[UDPWS_TRANSPORT_ADDR_SIZE] = "http://[";
int http_transport_addr_len;

uip_ipaddr_t ipaddr;
/*MUST have this name (refer uIP change for IPv4 multicast support)
 * TODO: should be "allocated" in contiki, not in the App Code */
uip_ipaddr_t uip_multicast_addr;
/* This is the global counter */
uint32_t instance_id;

#define PRINT6ADDR(addr) UDPWS_PRINTF(" %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x ", ((u8_t *)addr)[0], ((u8_t *)addr)[1], ((u8_t *)addr)[2], ((u8_t *)addr)[3], ((u8_t *)addr)[4], ((u8_t *)addr)[5], ((u8_t *)addr)[6], ((u8_t *)addr)[7], ((u8_t *)addr)[8], ((u8_t *)addr)[9], ((u8_t *)addr)[10], ((u8_t *)addr)[11], ((u8_t *)addr)[12], ((u8_t *)addr)[13], ((u8_t *)addr)[14], ((u8_t *)addr)[15])

PROCESS_THREAD(udpws_process, ev, data) {
    PROCESS_BEGIN();
#ifdef UDPWS_MINIMAL_NET_IPV4_BUGFIX
    PROCESS_YIELD();
#endif /* UDPWS_MINIMAL_NET_IPV4_BUGFIX*/

#if UDPWS_WAIT_FOR_START
        UDPWS_PRINTF("Type Enter to start..."); //To have time to start whireshark for sniffing etc.
        getc(stdin);
#endif /*UDPWS_WAIT_FOR_START*/

        UDPWS_PRINTF("uDPWS started...\n");

#if UIP_CONF_IPV6
        uip_netif_addr_add(&multicastaddr, UIP_DEFAULT_PREFIX_LEN, 0, DHCP);
        UDPWS_PRINTF("Local IPv6 address: ");
        PRINT6ADDR(&uip_netif_physical_if.addresses[0].ipaddr);
        UDPWS_PRINTF("\n");
#else /* IPv4*/
        uip_setethaddr(ethaddr);
        UDPWS_PRINTF("Local IPv4 addresses: \n");
        /*Add Multicast Address
         * TODO: more generic with defines */
        uip_ipaddr(&uip_multicast_addr, 239,255,255,250);
#endif /* UIP_CONF_IPV6 */

        dpws.device = UDPWS_DEVICE;
        dpws_retrans_init();
        /* Instance ID which is also the global seed for random values */
        read_persistent_data(&instance_id);
        if (instance_id < 100000 || instance_id > 999999) {
            instance_id = 100000;
        }
        write_persistent_data(++instance_id);
        dpws_init_InstanceId_and_uuid(instance_id);
        DBG_PRINT_INFO("uDPWS initialized (instance: %i)...\n", instance_id);
        /* Start the DEVICE Process which does the real job */
        process_start(dpws.device->process, NULL);

        /* receive UDP from every one with every source port */
        udp_conn = udp_new(NULL, 0, NULL);
        udp_bind(udp_conn, HTONS(UDPWS_DISCOVERY_MULTICAST_PORT));

        /* TCP Initialization */
        tcp_listen(HTONS(UDPWS_PORT));

        etimer_set(&dpws_hello_timer, 1*CLOCK_SECOND);

        UDPWS_INIT_TIMER();
        while (1) {
            /* TODO: to be faster: etimer_expired as macro?*/
            PROCESS_WAIT_EVENT_UNTIL(ev == tcpip_event || etimer_expired(&dpws_hello_timer));
            if (ev != tcpip_event && etimer_expired(&dpws_hello_timer)) {
                /* poll udp to send hello */
                tcpip_poll_udp(udp_conn);
                continue;
            }
            if (uip_udpconnection()) {
                generate_transport_address(); /*TODO:*/
                /* this is a UDP connection call */
                if(uip_poll()) {
                    /* polled by event timer */
                    /* TODO: check if the poll command comes from the timer */
                    handle_wsd_hello();
                } else {
                    /* Incoming udp packet */
                    handle_udp();
                }
                continue;
            }
            /* MUST be a tcp connection event */
            /* if we get a new connection we alloc a new
             * dpws connection structure
             * TODO: multi conneciton support!!!*/
            if (uip_connected()) {
                generate_transport_address(); /*TODO:*/
                /*at the moment just one connection*/
                tcp_unlisten(HTONS(UDPWS_PORT));
                /* init the new connection */
                dpws.conn = &dpws.conns[0];
                http_init(&dpws.conn->hc);
                sendm_init(&dpws.conn->sm, sendm_mem, SENDM_MAX_ITEMS);
            }

            /* handle the current connection */
            dpws.conn = &dpws.conns[0];
            dpws.conn->tcp_conn = uip_conn;
            handle_tcp();

            /* check if a connection can be freed */
            if ((uip_aborted() || uip_closed() || uip_timedout())) {
                /* we can listen to a new connection */
                DBG_PRINT_INFO("connection closed or aborted (listen)");
                tcp_listen(HTONS(UDPWS_PORT));
                UDPWS_PRINT_TIME_LIST();
                UDPWS_RESET_TIME_LIST();
            }
        }

        PROCESS_END();
    }
    /*---------------------------------------------------------------------------*/

    /* return value: shall the connection be removed from the connection pool or not? */
int handle_tcp() {
    /*TODO: separate dpws and dpws_conn*/
    struct dpws_conn_s *dpws_conn = dpws.conn;
    struct sendm_s *sm = &dpws_conn->sm;
    struct http_con *hc = &dpws_conn->hc;

    /*TODO: not necessary to check this! */
    if (uip_conn->lport != dpws_conn->tcp_conn->lport) {
        DBG_PRINT_ERROR("Not the right port: should never happen");
        return 0;
    }
    /*TODO: not necessary to check this! */
    if (uip_poll()) {
        DBG_PRINT_WARNING("Poll event but polling is not enabled");
    }

    /* --- HTTP REQEST MODE ---*/
    if (uip_newdata()) {
        DBG_PRINT_VERBOSE("UIP new data (%i bytes)\n", uip_datalen());
        http_in(&dpws_conn->hc, sm, uip_appdata, uip_datalen());
    }

    /* --- HTTP PROCESS MODE ---*/
    if (dpws_conn->hc.state == HTTP_STATE_PROCESS) {
        char* buf_end = hc->buffer + hc->buf_len;
        /*TODO: better place*/
        stack_init(&dpws_conn->stack, hc->buffer + hc->buf_len, HTTP_BUFFER_SIZE - hc->buf_len);
        /* process soap msg in the http buffer */
        UDPWS_START_TIMER();
        int http_err = dpws_process(dpws.device, hc->buffer, buf_end, &dpws_conn->stack, sm, DPWS_FALSE);
        UDPWS_TAKE_TIME("dpws_process"); DBG_PRINT_INFO("finished dpws processing (err: %d)", http_err);
        http_finished_processing(hc, sm, http_err); /* add the http header */
        UDPWS_TAKE_TIME("http_finish_processing"); DBG_PRINT_INFO("finished http processing");
    }

    /* --- RESPONSE MODE (HTTP finished)---*/
    if (uip_acked()) {
        /* ack what we have send */
        sendm_ack_buf(sm);
    }

    /* there is something to send and all data is acked
     * or
     * a retransmission is necessary  */
    if ((sendm_all_acked(sm) && sendm_started(sm)) || uip_rexmit()) {
        sendm_get_send_buf(&dpws_conn->sm, uip_appdata, uip_mss());
        uip_send(uip_appdata, sendm_send_len(sm));
    }
    /* close the connection if all data is sent and
     * http is closed TODO: what is the right check? Just ONE needed */
    if (sendm_finished(sm) && http_closed(hc)) {
        uip_close();
//        /*XXX: try to avoid keep alive*/
//        uip_abort();
    }

    return 0;
}

/*---------------------------------------------------------------------------*/

void handle_udp() {
    DBG_PRINT_INFO("handle udp connection");

    /* To accept UDP packets from every IP and Port we need to set the
     * remote IP and Port to NULL/0 but when we send the answer we need
     * to set these IP Address and Port to the current Req-Address, send
     * the packet and set the IP Address and Port to NULL/0*/
#define UIP_IP_BUF   ((struct uip_ip_hdr *)&uip_buf[UIP_LLH_LEN])
#if UIP_CONF_IPV6
#define UIP_UDP_BUF  ((struct uip_udp_hdr *)&uip_buf[uip_l2_l3_hdr_len])
#define UIP_UDP_MAX_SIZE    (UIP_BUFSIZE - uip_l2_l3_hdr_len - UIP_IPUDPH_LEN)
#else /* IPv4*/
#define UIP_UDP_BUF	 ((struct uip_udpip_hdr *)&uip_buf[UIP_LLH_LEN])
#define UIP_UDP_MAX_SIZE    (UIP_BUFSIZE - UIP_LLH_LEN - UIP_IPUDPH_LEN)
#endif /* UIP_CONF_IPV6 */

    struct uip_udp_conn send_conn;
    struct sendm_s sm;
    uint16_t udp_send_len = 0;
    /* TODO: is this to heavy for the stack : global data???*/
    char sendm_udp_mem[UDPWS_DISCOVERY_MAX_ITEMS * SENDM_ITEM_SIZE];
    sendm_init(&sm, sendm_udp_mem, UDPWS_DISCOVERY_MAX_ITEMS);

    UDPWS_START_TIMER();

    stack_free(&discovery_stack);
    /* TODO: which length? max or current */
    //	dpws_process(dpws.device, uip_appdata, uip_appdata + UIP_UDP_MAX_SIZE, &discovery_stack, NULL, DPWS_TRUE);
    if (dpws_process(dpws.device, uip_appdata, uip_appdata + uip_datalen(), &discovery_stack, &sm, DPWS_TRUE)) {
        /* dpws_process failed -> no answer send (no udp error response) */
        DBG_PRINT_INFO("Received UDP message but no response will be send");
        return;
    }

    UDPWS_TAKE_TIME("dpws_process_udp");

    /* Send message */
    /* get msg len*/
    /*TODO: check for error:*/
    udp_send_len = sendm_get_len(&sm);
    /* check if the buffer is big enough */
    if (UIP_UDP_MAX_SIZE < udp_send_len) {
        DBG_PRINT_ERROR("UDP Response to big (%d bytes, max: %d)", udp_send_len, UIP_UDP_MAX_SIZE);
        /* TODO: Error Report?*/
        return;
    } DBG_PRINT_INFO("UDP send %d bytes (Max: %d)\n", udp_send_len, UIP_UDP_MAX_SIZE);

    /* fill the sendbuffer with the message */
    sendm_get_send_buf(&sm, uip_appdata, udp_send_len);
    /* prepare the send "connection" */
    bzero(&send_conn, sizeof(struct uip_udp_conn));
    send_conn.lport = htons(UDPWS_DISCOVERY_MULTICAST_PORT);
    send_conn.ttl = 255;
    /* fill the client ip addr. and port */
    uip_ipaddr_copy(&send_conn.ripaddr, &UIP_IP_BUF->srcipaddr);
    send_conn.rport = UIP_UDP_BUF->srcport;

    DPWS_LOG_RESPONSE(uip_appdata, udp_send_len);

    uip_udp_packet_send_fix(&send_conn, udp_send_len);
    /* TODO: if the client is not in the ARP List, the UDP message will not be send!!! */
    UDPWS_PRINT_TIME_LIST(); UDPWS_RESET_TIME_LIST();
}

void handle_wsd_hello() {
#if UDPWS_CONF_WSDISCOVERY_HELLO
    DBG_PRINT_INFO("hello timer fired (%i)", dpws_hello_timer_count);
    /* send hello */
    struct uip_udp_conn send_conn;
    struct sendm_s sm;
    uint16_t udp_send_len = 0;
    /* TODO: is this to heavy for the stack : global data???*/
    char sendm_udp_mem[UDPWS_DISCOVERY_MAX_ITEMS * SENDM_ITEM_SIZE];
    sendm_init(&sm, sendm_udp_mem, UDPWS_DISCOVERY_MAX_ITEMS);

    /* generate msg */
    dpws_gen_hello(dpws.device, &sm);

    /* Send message */
    /* get msg len*/
    /*TODO: check for error:*/
    udp_send_len = sendm_get_len(&sm);
    /* check if the buffer is big enough */
    if (UIP_UDP_MAX_SIZE < udp_send_len) {
        DBG_PRINT_ERROR("UDP Msg to big (%d bytes, max: %d)", udp_send_len, UIP_UDP_MAX_SIZE);
        /* TODO: Error Report?*/
        return;
    }

    /* fill the sendbuffer with the message */
    sendm_get_send_buf(&sm, uip_appdata, udp_send_len);
    /* prepare the send "connection" */
    bzero(&send_conn, sizeof(struct uip_udp_conn));
    send_conn.lport = htons(UDPWS_DISCOVERY_MULTICAST_PORT);
    send_conn.ttl = 255;
    /* fill the client ip addr. and port */
    uip_ipaddr_copy(&send_conn.ripaddr, &multicastaddr);
    send_conn.rport = htons(UDPWS_DISCOVERY_MULTICAST_PORT);

    DPWS_LOG_REQUEST(uip_appdata, udp_send_len);
    uip_udp_packet_send_fix(&send_conn, udp_send_len);
    /* TODO: if the client is not in the ARP Table, the UDP message will not be send!!! */

    /* reset timer */
    dpws_hello_timer_count--;
    if (dpws_hello_timer_count) {
        etimer_restart(&dpws_hello_timer);
    }
    return;
#endif /* UDPWS_CONF_WSDISCOVERY_HELLO */
}

/*---------------------------------------------------------------------------*/
void itohexa(uint8_t n, char *s) {
    uint8_t h;
    uint8_t mask;
    int i;
    char c;
    h = n;
    for (i = 1; i >= 0; i--) {
        mask = h & 0x0F;
        if ((mask) > 9) {
            c = (mask) + ('a' - 10);
        } else {
            c = (mask) + '0';
        }
        s[i] = c;
        h = h >> 4;
    }
}

void generate_transport_address() {
#if UIP_CONF_IPV6
    int i, c, t;
    /* http_transport_addr is initialized with "http://[" */
    c = 8;
    t = 0; /* toggle every second val */
    for (i = 0; i < 16; i++) {
        itohexa(uip_netif_physical_if.addresses[UDPWS_INTERFACE_NO].ipaddr.u8[i], &http_transport_addr[c]);
        c += 2;
        if (t) {
            http_transport_addr[c] = ':';
            c++;
            t = 0;
        } else {
            t = 1;
        }
    }
    http_transport_addr[47] = ']';
    http_transport_addr[48] = ':';
    itoa2(UDPWS_PORT, &http_transport_addr[49]);
    http_transport_addr[53] = '/';
    http_transport_addr[54] = '\0';
    http_transport_addr_len = 54;
    //	sprintf(http_transport_addr, "http://[%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x]:%d/",
    //			uip_netif_physical_if.addresses[0].ipaddr.u8[0],
    //			uip_netif_physical_if.addresses[0].ipaddr.u8[1],
    //			uip_netif_physical_if.addresses[0].ipaddr.u8[2],
    //			uip_netif_physical_if.addresses[0].ipaddr.u8[3],
    //			uip_netif_physical_if.addresses[0].ipaddr.u8[4],
    //			uip_netif_physical_if.addresses[0].ipaddr.u8[5],
    //			uip_netif_physical_if.addresses[0].ipaddr.u8[6],
    //			uip_netif_physical_if.addresses[0].ipaddr.u8[7],
    //			uip_netif_physical_if.addresses[0].ipaddr.u8[8],
    //			uip_netif_physical_if.addresses[0].ipaddr.u8[9],
    //			uip_netif_physical_if.addresses[0].ipaddr.u8[10],
    //			uip_netif_physical_if.addresses[0].ipaddr.u8[11],
    //			uip_netif_physical_if.addresses[0].ipaddr.u8[12],
    //			uip_netif_physical_if.addresses[0].ipaddr.u8[13],
    //			uip_netif_physical_if.addresses[0].ipaddr.u8[14],
    //			uip_netif_physical_if.addresses[0].ipaddr.u8[15], UDPWS_PORT);
#else /* IPv4*/
    /*TODO: use itoa2 instead of sprintf!!! */
    sprintf(http_transport_addr, "http://%d.%d.%d.%d:%d/", uip_hostaddr.u8[0], uip_hostaddr.u8[1], uip_hostaddr.u8[2], uip_hostaddr.u8[3], UDPWS_PORT);
    http_transport_addr_len = strlen(http_transport_addr);
#endif /* UIP_CONF_IPV6 */
    DBG_PRINT_INFO("http transport address is %s", http_transport_addr);
}

/*---------------------------------------------------------------------------*/

void uip_udp_packet_send_fix(struct uip_udp_conn *c, int len) {
#if UIP_UDP
    if (len > UIP_BUFSIZE) {
        DBG_PRINT_ERROR("len greater than buffer size: should never happen! Check your implementation for buffer overflow!");
        return;
    }

    uip_udp_conn = c;
    uip_slen = len;
    /*NO Copy... the application writes directly to the buffer uip_sappdata == &uip_buf[UIP_LLH_LEN + UIP_IPUDPH_LEN]*/
    //  memcpy(&uip_buf[UIP_LLH_LEN + UIP_IPUDPH_LEN], data, len > UIP_BUFSIZE? UIP_BUFSIZE: len);
    /* comment: to use UIP_BUFSIZE is a bug and can result in a buffer overflow, UIP_BUFSIZE should be (UIP_BUFSIZE - UIP_LLH_LEN - UIP_IPUDPH_LEN)*/
    uip_process(UIP_UDP_SEND_CONN);
#if UIP_CONF_IPV6 //math
    tcpip_ipv6_output();
#else
    if(uip_len > 0) {
        tcpip_output();
    }
#endif
    uip_slen = 0;
#endif /* UIP_UDP */
}

